package com.coderworm.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.util.zip.CRC32;

/**
 * Netty客户端核心处理器：完整实现身份验证、文件元数据发送、文件内容分块传输、CRC校验
 * 特性：
 * 1. 密码SHA-256加盐加密传输，避免明文泄露
 * 2. 大文件分块发送（32KB/块），防止内存溢出
 * 3. 与服务端一致的CRC32校验逻辑，确保文件完整性
 * 4. 完整的异常处理，避免资源泄漏
 */
public class RtcmClientHandler extends ChannelInboundHandlerAdapter {
    // 客户端配置参数（初始化时传入，与服务端保持一致）
    private final String userName;       // 授权用户名（需在服务端AUTH_USERS中存在）
    private final String password;       // 授权密码（明文，仅本地存储，传输时加密）
    private final String salt;           // 密码加盐值（必须与服务端SALT完全相同）
    private final String rtcmFilePath;   // 本地RTCM文件路径（需存在且为有效文件）

    // 状态变量：控制通信流程，避免重复操作
    private boolean isAuthSent = false;  // 是否已发送身份验证信息
    private boolean isFileSent = false;  // 是否已发送文件内容

    /**
     * 构造方法：初始化客户端配置
     * @param userName 授权用户名
     * @param password 授权密码（明文）
     * @param salt 密码加盐值（与服务端一致）
     * @param rtcmFilePath 本地RTCM文件路径
     * @throws IllegalArgumentException 若参数非法（如文件不存在），直接抛出异常
     */
    public RtcmClientHandler(String userName, String password, String salt, String rtcmFilePath) {
        // 参数合法性校验（提前拦截非法配置）
        if (userName == null || userName.trim().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (password == null || password.trim().isEmpty()) {
            throw new IllegalArgumentException("密码不能为空");
        }
        if (salt == null || salt.trim().isEmpty()) {
            throw new IllegalArgumentException("加盐值不能为空（需与服务端一致）");
        }
        File rtcmFile = new File(rtcmFilePath);
        if (!rtcmFile.exists() || !rtcmFile.isFile()) {
            throw new IllegalArgumentException("RTCM文件不存在或非法：" + rtcmFilePath);
        }
        if (rtcmFile.length() <= 0) {
            throw new IllegalArgumentException("RTCM文件为空：" + rtcmFilePath);
        }

        // 初始化参数
        this.userName = userName.trim();
        this.password = password.trim();
        this.salt = salt.trim();
        this.rtcmFilePath = rtcmFilePath;
    }

    /**
     * 连接建立成功后触发：发送身份验证信息（仅发送一次）
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 仅在未发送身份验证时执行
        if (!isAuthSent) {
            System.out.println("[客户端] 连接服务端成功，开始发送身份验证信息");

            // 1. 计算密码SHA-256密文（加盐，与服务端校验逻辑一致）
            String pwdHash = calculateSha256WithSalt(password, salt);
            System.out.println("[客户端] 密码SHA-256密文（加盐）：" + pwdHash);

            // 2. 构造身份验证数据包（格式：用户名长度2字节 + 用户名UTF-8 + 密码密文64字节）
            ByteBuf authBuf = ctx.alloc().buffer();
            byte[] userNameBytes = this.userName.getBytes(StandardCharsets.UTF_8);

            // 2.1 写入用户名长度（2字节，大端序）
            authBuf.writeShort(userNameBytes.length);
            // 2.2 写入用户名内容
            authBuf.writeBytes(userNameBytes);
            // 2.3 写入密码密文（SHA-256固定64字节，提前校验避免格式错误）
            byte[] pwdHashBytes = pwdHash.getBytes(StandardCharsets.UTF_8);
            if (pwdHashBytes.length != 64) {
                throw new Exception("密码密文长度错误（需64字节，实际：" + pwdHashBytes.length + "字节），请检查SHA-256算法实现");
            }
            authBuf.writeBytes(pwdHashBytes);

            // 3. 异步发送身份验证数据（Netty自动处理IO，无需阻塞等待）
            ctx.writeAndFlush(authBuf);
            isAuthSent = true;
            System.out.println("[客户端] 身份验证信息发送完成，等待服务端响应");
        }
        super.channelActive(ctx);
    }

    /**
     * 接收服务端响应时触发：处理验证结果、发送文件
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 转换为Netty ByteBuf（接收服务端数据的标准格式）
        ByteBuf responseBuf = (ByteBuf) msg;
        try {
            // 读取服务端响应内容（转为字符串）
            byte[] responseBytes = new byte[responseBuf.readableBytes()];
            responseBuf.readBytes(responseBytes);
            String serverResponse = new String(responseBytes, StandardCharsets.UTF_8);
            System.out.println("[客户端] 收到服务端响应：" + serverResponse);

            // 分支1：处理身份验证结果
            if (!isFileSent && serverResponse.startsWith("AUTH_SUCCESS")) {
                System.out.println("[客户端] 身份验证通过，开始准备发送RTCM文件");
                // 发送文件（元数据 + 内容）
                sendRtcmFileToServer(ctx);
                isFileSent = true;
            }

            // 分支2：处理文件接收结果（成功/失败）
            else if (isFileSent) {
                if (serverResponse.startsWith("SUCCESS")) {
                    System.out.println("[客户端] 文件发送成功，服务端处理完成：" + serverResponse);
                } else if (serverResponse.startsWith("ERROR")) {
                    System.err.println("[客户端] 文件发送失败，服务端提示：" + serverResponse);
                }
                // 无论成功与否，发送完成后主动关闭连接（避免长期占用资源）
                ctx.close();
                System.out.println("[客户端] 连接已主动关闭");
            }

            // 分支3：处理服务端错误响应（如IP未授权、密码错误）
            else if (serverResponse.startsWith("ERROR")) {
                System.err.println("[客户端] 服务端拒绝请求：" + serverResponse);
                ctx.close();
                System.out.println("[客户端] 连接已关闭（因服务端错误）");
            }

        } finally {
            // 关键：释放ByteBuf资源，避免Netty内存泄漏
            responseBuf.release();
        }
    }

    /**
     * 发送RTCM文件到服务端：分两步发送（元数据 + 内容）
     */
    private void sendRtcmFileToServer(ChannelHandlerContext ctx) throws Exception {
        File rtcmFile = new File(rtcmFilePath);
        String fileName = rtcmFile.getName();
        long fileTotalSize = rtcmFile.length();

        // 步骤1：发送文件元数据（格式：文件名长度2字节 + 文件名 + 文件大小8字节 + CRC32 4字节）
        ByteBuf metaBuf = ctx.alloc().buffer();
        byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_8);

        // 1.1 写入文件名长度
        metaBuf.writeShort(fileNameBytes.length);
        // 1.2 写入文件名
        metaBuf.writeBytes(fileNameBytes);
        // 1.3 写入文件总大小（8字节，大端序，支持最大9EB文件）
        metaBuf.writeLong(fileTotalSize);
        // 1.4 写入文件CRC32校验码（4字节，与服务端校验逻辑一致）
        long fileCrc = calculateFileCrc32(rtcmFile);
        metaBuf.writeInt((int) fileCrc);

        // 发送元数据并打印日志
        ctx.writeAndFlush(metaBuf);
        System.out.println("[客户端] 文件元数据发送完成：");
        System.out.println("  - 文件名：" + fileName);
        System.out.println("  - 文件大小：" + fileTotalSize + " 字节");
        System.out.println("  - CRC32校验码：" + fileCrc);

        // 步骤2：分块发送文件内容（32KB/块，避免大文件占用内存）
        sendFileContentInChunks(ctx, rtcmFile, fileTotalSize);
    }

    /**
     * 分块发送文件内容（核心优化：避免一次性加载大文件到内存）
     * @param ctx 通道上下文
     * @param file 要发送的RTCM文件
     * @param totalSize 文件总大小（字节）
     */
    private void sendFileContentInChunks(ChannelHandlerContext ctx, File file, long totalSize) throws Exception {
        // 32KB缓冲区：平衡IO效率（减少系统调用）和内存占用（避免OOM）
        byte[] buffer = new byte[32 * 1024];
        long sentSize = 0;  // 已发送字节数
        int bytesRead;      // 每次读取的字节数

        // 使用缓冲流读取文件（提升本地文件读取效率）
        try (FileInputStream fis = new FileInputStream(file);
             BufferedInputStream bis = new BufferedInputStream(fis, buffer.length)) {

            System.out.println("[客户端] 开始分块发送文件内容（32KB/块）");

            // 循环读取并发送，直到文件读取完毕
            while ((bytesRead = bis.read(buffer)) != -1) {
                // 从Netty内存池分配ByteBuf（避免频繁创建对象导致GC）
                ByteBuf contentBuf = ctx.alloc().buffer(bytesRead);
                contentBuf.writeBytes(buffer, 0, bytesRead);

                // 异步发送当前块（Netty自动管理发送队列，无需等待）
                ctx.writeAndFlush(contentBuf);

                // 更新已发送大小并打印进度（每10%更新一次，避免频繁输出）
                sentSize += bytesRead;
                int progress = (int) ((sentSize * 100.0) / totalSize);
                if (progress % 10 == 0 && progress != (int) (((sentSize - bytesRead) * 100.0) / totalSize)) {
                    System.out.println("[客户端] 文件发送进度：" + progress + "%（已发送：" + sentSize + "/" + totalSize + " 字节）");
                }
            }

            // 发送完成日志
            System.out.println("[客户端] 文件内容发送完成：");
            System.out.println("  - 实际发送字节数：" + sentSize);
            System.out.println("  - 预期文件大小：" + totalSize);
            System.out.println("  - 等待服务端CRC校验结果...");

        } catch (Exception e) {
            throw new Exception("分块发送文件失败：" + e.getMessage(), e);
        }
    }

    /**
     * 计算文件的CRC32校验码（与服务端RtcmFileHandler的校验逻辑完全一致）
     */
    private long calculateFileCrc32(File file) throws Exception {
        CRC32 crc32 = new CRC32();
        byte[] buffer = new byte[32 * 1024];  // 与文件读取缓冲区一致，提升效率
        int bytesRead;

        try (FileInputStream fis = new FileInputStream(file);
             BufferedInputStream bis = new BufferedInputStream(fis, buffer.length)) {

            while ((bytesRead = bis.read(buffer)) != -1) {
                crc32.update(buffer, 0, bytesRead);  // 逐块更新CRC值
            }

        } catch (Exception e) {
            throw new Exception("计算文件CRC32失败（文件路径：" + file.getAbsolutePath() + "）：" + e.getMessage(), e);
        }

        long crcValue = crc32.getValue();
        System.out.println("[客户端] 文件CRC32计算完成：" + crcValue);
        return crcValue;
    }

    /**
     * 计算字符串的SHA-256哈希值（加盐，与服务端calculateSha256方法完全一致）
     * @param content 原始内容（如密码）
     * @param salt 加盐值
     * @return 64位小写十六进制哈希字符串
     */
    private String calculateSha256WithSalt(String content, String salt) throws Exception {
        if (content == null || salt == null) {
            throw new IllegalArgumentException("SHA-256计算：内容和加盐值不能为空");
        }

        try {
            // 初始化SHA-256消息摘要器
            java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256");
            // 加盐：原始内容 + 盐值（防止彩虹表破解）
            String saltedContent = content + salt;
            // 计算哈希值（字节数组形式）
            byte[] hashBytes = digest.digest(saltedContent.getBytes(StandardCharsets.UTF_8));
            // 转为64位小写十六进制字符串（SHA-256固定输出256位=32字节=64位十六进制）
            StringBuilder sb = new StringBuilder(64);
            for (byte b : hashBytes) {
                sb.append(String.format("%02x", b));  // 不足2位补0，确保格式统一
            }

            String hashStr = sb.toString();
            // 校验长度（避免算法异常导致的格式错误）
            if (hashStr.length() != 64) {
                throw new Exception("SHA-256计算结果长度错误（预期64位，实际：" + hashStr.length() + "位）");
            }
            return hashStr;

        } catch (java.security.NoSuchAlgorithmException e) {
            // 理论上现代JVM均支持SHA-256，若抛出此异常需检查JVM环境
            throw new Exception("不支持SHA-256算法（JVM环境异常）：" + e.getMessage(), e);
        }
    }

    /**
     * 客户端发生异常时触发：关闭连接并释放所有资源
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 打印异常信息（便于排查问题）
        System.err.println("[客户端异常] 发生错误：" + cause.getMessage());
        cause.printStackTrace();

        // 关闭连接（避免僵尸连接占用端口和内存）
        if (ctx.channel().isActive() || ctx.channel().isOpen()) {
            ctx.close();
            System.err.println("[客户端异常] 已关闭异常连接");
        }
    }

    /**
     * 连接关闭时触发：清理状态变量（可选，用于后续重连场景）
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[客户端] 连接已关闭（正常/异常触发）");
        // 重置状态变量（若后续需要重连，可直接复用当前Handler）
        this.isAuthSent = false;
        this.isFileSent = false;
        super.channelInactive(ctx);
    }
}